package com.shanzmoo.platform.manage.api.manager;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.shanzmoo.base.constant.Constant;
import com.shanzmoo.base.util.RedisUtil;
import com.shanzmoo.core.util.RedisCoreKeyUtils;
import com.shanzmoo.platform.manage.api.util.RedisKeyUtils;
import io.jsonwebtoken.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.util.Collection;
import java.util.Date;
import java.util.List;

/**
 * JWT 令牌管理：生成、验证、获取
 * @author: Gzhao 2020/11/9
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class JwtManager {
    /**
     * 创建JWT
     *
     * @param authentication 用户认证信息
     * @param rememberMe     记住我
     * @return JWT
     */
//    public String createJWT(Authentication authentication, Boolean rememberMe) {
//        AuthUser authUser = JSON.parseObject(JSON.toJSONString(authentication.getPrincipal()), AuthUser.class);
//        return createJWT(rememberMe, authUser.getUserId(), authUser.getUsername(), authUser.getRoleIds(), authUser.getAuthorities());
//    }

    /**
     * 根据 jwt 获取用户名
     *
     * @param jwt JWT
     * @return 用户名
     */
    public String getUsernameFromJWT(String jwt) {
        Claims claims = parseJWT(jwt);
        return claims.getSubject();
    }


    /**
     * 从请求中获取到 jwt 信息
     * @param request
     * @return
     */
    public String getJwtFromRequest(HttpServletRequest request) {
        String bearerToken = request.getHeader("Authorization");
        if (StrUtil.isNotBlank(bearerToken) && bearerToken.startsWith("Bearer ")) {
            return bearerToken.substring(7);
        }
        String token = request.getParameter("token");
        if(StrUtil.isNotBlank(token)){
            return token;
        }
        return null;
    }

    /**
     * 删除JWT
     * @param request
     */
    public void invalidateJWT(HttpServletRequest request) {
        String jwt = getJwtFromRequest(request);
        if (StrUtil.isNotBlank(jwt)){
            String username = getUsernameFromJWT(jwt);
            // 从redis中清除JWT
            RedisUtil.del(RedisCoreKeyUtils.installSubectJwtKey(username));
        }
    }

    public String createJWT(Boolean rememberMe, Integer userId, String subject, List<Integer> roleIds,
                            Collection<? extends GrantedAuthority> authorities) {
        return createJWT(rememberMe, userId, subject, roleIds, authorities, null);
    }

    public String createJWT(Boolean rememberMe, Integer userId, String subject, List<Integer> roleIds,
                            Collection<? extends GrantedAuthority> authorities, String redisKey) {
        Date now = new Date();
        JwtBuilder builder = Jwts.builder()
                .setId(String.valueOf(userId))
                .setSubject(subject)
                .setIssuedAt(now)
                .signWith(SignatureAlgorithm.HS256, Constant.JWT_KEY)
                .claim("roles", roleIds)
                .claim("authorities", authorities);

        // 设置过期时间
        Long ttl = rememberMe ? Constant.JWT_REMEMBER : Constant.JWT_DELAYED;
        if (ttl > 0) {
            builder.setExpiration(DateUtil.offsetMillisecond(now, ttl.intValue()));
        }
        String jwt = builder.compact();
        log.info("subject :{}, 生成令牌：{}", subject, jwt);

        if (StrUtil.isBlank(redisKey)) {
            redisKey = RedisCoreKeyUtils.installSubectJwtKey(subject);
        }
        // 将生成的JWT保存至Redis
        RedisUtil.set(redisKey,jwt,ttl/1000/60);
        return jwt;
    }

    /**
     * 解析JWT
     * @param jwt
     * @return
     */
    public Claims parseJWT(String jwt) {
        try {
            Claims claims = Jwts.parser()
                    .setSigningKey(Constant.JWT_KEY)
                    .parseClaimsJws(jwt)
                    .getBody();

            String redisKey = RedisKeyUtils.installSubectJwtKey(claims.getSubject());
            // 校验redis中的JWT是否存在
            if (!RedisUtil.has(redisKey)) {
                throw new RuntimeException("Token 不存在或已过期");
            }

            // 校验redis中的JWT是否与当前的一致，不一致则代表用户已注销/用户在不同设备登录，均代表JWT已过期
            String redisToken = RedisUtil.get(redisKey);
            if (!StrUtil.equals(jwt, redisToken)) {
                throw new RuntimeException("Token 信息异常");
            }
            return claims;
        } catch (JwtException e) {
            log.error("Token 数据异常，解析失败");
            throw new RuntimeException("Token 数据异常，解析失败");
        }
    }
}
